﻿using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Data.Linq;
using System.Linq;
using System.Text;
using System.Transactions;

using Ordinaire.Commerce;
using Ordinaire.Commerce.Data;
using Ordinaire.Commerce.Db;
using Ordinaire.Social;
using Ordinaire.Social.Data;
using Ordinaire.Taxonomy;

namespace Ordinaire.Commerce.VendorRelation
{
    /// <summary>
    /// Manages storage of business relation information in a SQL Database server.
    /// </summary>
    [Export(typeof(IVendorRelation))]
    public class VendorRelationManager : ContactManager, IVendorRelation
    {
        #region Enum

        /// <summary>
        /// List of tags used by this class.
        /// </summary>
        public enum Tag
        {
            Supplier
        }

        #endregion

        private Lazy<JunctionDataManager<CContext>> junctionDataManager;

        #region Constructor

        /// <summary>
        /// Creates new instance of Ordinaire.Commerce.VendorRelation.VendorRelationManager.
        /// </summary>
        public VendorRelationManager()
        {
            this.junctionDataManager = new Lazy<JunctionDataManager<CContext>>();
        }

        #endregion

        #region Public methods

        /// <summary>
        /// Creates and returns new supplier contact with the specified name as organisation contact.
        /// </summary>
        /// <typeparam name="TOrganisation">type of Ordinaire.Social.Organisation</typeparam>
        /// <param name="name">the supplier name</param>
        /// <returns>new supplier contact</returns>
        public TOrganisation CreateSupplierContactAsOrganisation<TOrganisation>(string name)
            where TOrganisation : Organisation
        {
            bool success    = false;
            Type entityType = typeof(TOrganisation);

            EntityOrganiser entityOrganiser = new EntityOrganiser();

            using (TransactionScope transaction = new TransactionScope())
            {
                TOrganisation organisation = CreateOrganisationContact<TOrganisation>(name);
                if (organisation != null)
                {
                    ShareContext(entityOrganiser);
                    success = entityOrganiser.Tag(organisation, Tag.Supplier.Name());
                }

                // Commit transaction, if successful
                if (success)
                {
                    transaction.Complete();
                }

                return organisation;
            }
        }

        /// <summary>
        /// Creates and returns new supplier contact with the specified name as person contact.
        /// </summary>
        /// <typeparam name="TPerson">type of Ordinaire.Social.Person</typeparam>
        /// <param name="fullName">the supplier fullname</param>
        /// <returns>new supplier contact</returns>
        public TPerson CreateSupplierContactAsPerson<TPerson>(string fullName)
            where TPerson : Person
        {
            fullName.ThrowIfNullOrEmpty<ArgumentException>(String.Format(ExceptionMessage.IsNullOrEmpty, "fullName"));

            string[] names = fullName.ToNames();
            return CreateSupplierContactAsPerson<TPerson>(names[0], names[1], names[2]);
        }

        /// <summary>
        /// Creates and returns new supplier contact with the specified names (first, middle, last) as person contact.
        /// </summary>
        /// <typeparam name="TPerson">type of Ordinaire.Social.Person</typeparam>
        /// <param name="firstName">the person's first name</param>
        /// <param name="middleName">the person's middle name</param>
        /// <param name="lastName">the person's last name</param>
        /// <returns>new supplier contact</returns>
        public TPerson CreateSupplierContactAsPerson<TPerson>(string firstName, string middleName, string lastName)
            where TPerson : Person
        {
            firstName.ThrowIfNullOrEmpty<ArgumentException>(String.Format(ExceptionMessage.IsNullOrEmpty, "firstName"));

            bool success = false;
            Type entityTYpe = typeof(TPerson);

            EntityOrganiser entityOrganiser = new EntityOrganiser();

            using (TransactionScope transaction = new TransactionScope())
            {
                TPerson organisation = CreatePersonContact<TPerson>(firstName, middleName, lastName);
                if (organisation != null)
                {
                    ShareContext(entityOrganiser);
                    success = entityOrganiser.Tag(organisation, Tag.Supplier.Name());
                }

                // Commit transaction, if successful
                if (success)
                {
                    transaction.Complete();
                }

                return organisation;
            }
        }

        /// <summary>
        /// Creates new supplier contact in the data source.
        /// </summary>
        /// <param name="name">supplier name</param>
        /// <param name="isOrganisation">value indicating whether the contact</param>
        /// <returns>true if creation is successful; otherwise false</returns>
        /// <remarks>Person name will be broken into three names: first as first name, last as last name, and the rest as middle name(s)</remarks>
        public bool CreateSupplierContact(string name, bool isOrganisation = true)
        {
            bool success = false;
            EntityOrganiser entityOrganiser = new EntityOrganiser();

            using (TransactionScope transaction = new TransactionScope())
            {
                if (isOrganisation)
                {
                    Organisation organisation = CreateOrganisationContact(name);
                    if (organisation != null)
                    {
                        ShareContext(entityOrganiser);
                        success = entityOrganiser.Tag(organisation, Tag.Supplier.Name());
                    }
                }
                else
                {
                    Person person = CreatePersonContact(name);
                    if (person != null)
                    {
                        ShareContext(entityOrganiser);
                        success = entityOrganiser.Tag(person, Tag.Supplier.Name());
                    }
                }

                // Commit transaction, if successful
                if (success)
                {
                    transaction.Complete();
                }

                return success;
            }
        }

        #endregion

        #region IVendorRelation methods

        /// <summary>
        /// Adds new record into organisation goods catalog.
        /// </summary>
        /// <param name="organisationId">the organisation id</param>
        /// <param name="goodId">the good id</param>
        /// <returns>id of the data</returns>
        public int AddOrganisationGoodCatalog(int organisationId, int goodId)
        {
            var organisationGoodData = junctionDataManager.Value.GetData<OrganisationGoods, Organisations, Goods, int, string>(organisationId, goodId);
            if (organisationGoodData == null)
            {
                organisationGoodData = new OrganisationGoods
                {
                    OrganisationId = organisationId,
                    GoodId         = goodId
                };
                dataManager.Insert<OrganisationGoods, int, string>(organisationGoodData);
            }
            return organisationGoodData.Id;
        }

        /// <summary>
        /// Adds new record into person goods catalog.
        /// </summary>
        /// <param name="personId">the person id</param>
        /// <param name="goodId">the good id</param>
        /// <returns>id of the data</returns>
        public int AddPersonGoodCatalog(int personId, int goodId)
        {
            var personGoodData = junctionDataManager.Value.GetData<PersonGoods, People, Goods, int, string>(personId, goodId);
            if (personGoodData == null)
            {
                personGoodData = new PersonGoods
                {
                    PersonId = personId,
                    GoodId   = goodId
                };
                dataManager.Insert<PersonGoods, int, string>(personGoodData);
            }
            return personGoodData.Id;
        }

        /// <summary>
        /// Adds or updates vendor catalog.
        /// </summary>
        /// <param name="vendorId">the vendor id: person or organisation id</param>
        /// <param name="goodId">the good id</param>
        /// <param name="valuePerUnit">the value of good per unit</param>
        /// <param name="isOrganisation">an indicator whether vendor is an organisation</param>
        /// <returns>the reference id of the entry</returns>
        public int AddOrUpdateGoodCatalog(int vendorId, int goodId, decimal valuePerUnit = 0, bool isOrganisation = true)
        {
            vendorId.ThrowIfEmpty<ArgumentException>(String.Format(ExceptionMessage.Invalid, String.Format("vendorId '{0}'", vendorId)));
            goodId.ThrowIfEmpty<ArgumentException>(String.Format(ExceptionMessage.Invalid, String.Format("goodId '{0}'", goodId)));

            if (isOrganisation)
            {
                return AddOrUpdateOrganisationGoodCatalog(vendorId, goodId, valuePerUnit);
            }

            return AddOrUpdatePersonGoodCatalog(vendorId, goodId, valuePerUnit);
        }

        #endregion

        #region Private methods

        private int AddOrUpdateOrganisationGoodCatalog(int organisationId, int goodId, decimal valuePerUnit)
        {
            OrganisationGoods organisationGoodData = null;

            organisationGoodData = junctionDataManager.Value.GetData<OrganisationGoods, Organisations, Goods, int, string>(organisationId, goodId);
            if (organisationGoodData == null)
            {
                organisationGoodData = new OrganisationGoods
                {
                    OrganisationId = organisationId,
                    GoodId         = goodId,
                    ValuePerUnit   = valuePerUnit
                };
                dataManager.Insert<OrganisationGoods, int, string>(organisationGoodData);
            }
            else
            {
                organisationGoodData.ValuePerUnit = valuePerUnit;
                junctionDataManager.Value.Update<OrganisationGoods, int, string>(organisationGoodData);
            }

            return organisationGoodData.Id;
        }

        private int AddOrUpdatePersonGoodCatalog(int personId, int goodId, decimal valuePerUnit)
        {
            PersonGoods personGoodData = null;

            personGoodData = junctionDataManager.Value.GetData<PersonGoods, People, Goods, int, string>(personId, goodId);
            if (personGoodData == null)
            {
                personGoodData = new PersonGoods
                {
                    PersonId     = personId,
                    GoodId       = goodId,
                    ValuePerUnit = valuePerUnit
                };
                dataManager.Insert<PersonGoods, int, string>(personGoodData);
            }
            else
            {
                personGoodData.ValuePerUnit = valuePerUnit;
                junctionDataManager.Value.Update<PersonGoods, int, string>(personGoodData);
            }

            return personGoodData.Id;
        }

        #endregion
    }
}
